-- 无直观含义的计算模块
-- 或者是定制化计算模块
local mis_env = require('modules.env.env')
local uu = require('modules.commhm.utils')
local log = require("modules.commhm.log");
local string_sub = string.sub
local string_char = string.char
local string_byte = string.byte
local string_format = string.format
local math_min = math.min
local math_pow = math.pow
local math_fmod = math.fmod
local math_floor = math.floor
local math_random = math.random
local table_concat = table.concat
local table_insert = table.insert
local os_date = os.date
local ngx = ngx

local n_A = string_byte("A")
local n_Z = string_byte("Z")
local n_0 = string_byte("0")
local n_9 = string_byte("9")
local n_MAX_CODE = 60466175
local n_INIT_CODE = 38032637

local t_cache = ngx.shared.misCache
local t_coopCache = ngx.shared.misCoop

local mis_log = nil
local mis_config = nil

--------------------------------------------------------------
local mis_calcu = {
	s_name = string.match(debug.getinfo(1).short_src, '.*/(.*).lua');	-- 模块名称
}
local this = mis_calcu
local n_m_count = 0
local n_m_str = nil
local s_m_myb64chars = 'Vg21WQ5KdRt0yNpc' .. 'r9m4O3PoHaZvsLe' .. 'CY8FjSwiTkUbuEBIJ' .. 'lAG7fqXM6xDnzh-;'
local t_m_hash = {}

--------------------------------------------------------------
-- 说明：模块包含
-- 参数：模块表
this.modules_include = function(t_modules)
	mis_log = t_modules.mis_log
	mis_config = t_modules.mis_config
end

--------------------------------------------------------------
-- 说明：计算S2盐
-- 参数：n_uin
--		n_s2t
-- 返回：s 		盐
local function gen_S2Salt(n_uin, n_s2t)
	if n_s2t and #n_s2t == 10 then
		return ngx.md5(n_uin .. mis_config.s_saltSecretKey .. n_s2t)
	else
		return '#_php_miniw_2016_#'
	end
end

--------------------------------------------------------------
-- 说明：url解密
-- 参数：s_str64		待解密串
-- 返回：s			解密串
this.decode_s7Url = function(s_str64)
	local t_tmp
	local s_str, s_b64chars, s_str2 = ""
	local n_data, n_strCount
	if  this.s_myb64charsTemp then
		t_tmp = this.s_myb64charsTemp
	else
		s_b64chars = s_m_myb64chars
		t_tmp = {}
		for i = 1, 64 do
			t_tmp[string_sub(s_b64chars, i, i)] = i
		end
		t_tmp['_'] = 0
		this.s_myb64charsTemp = t_tmp
	end
	for i = 1, #s_str64, 4 do
		if i > #s_str64 then
			break
		end
		n_data = 0
		n_strCount = 0
		for j = 0, 3 do
			local s_str2 = string_sub(s_str64, i + j, i + j)
			if not t_tmp[s_str2] then
				return
			end
			if t_tmp[s_str2] < 1 then
				n_data = n_data * 64
			else
				n_data = n_data * 64 + t_tmp[s_str2] - 1
				n_strCount = n_strCount + 1
			end
		end
		for j = 16, 0, -8 do
			if n_strCount > 0 then
				s_str = s_str .. string_char(math_floor(n_data / math_pow(2, j)))
				n_data = n_data % math_pow(2, j)
				n_strCount = n_strCount - 1
			end
		end
	end
	return s_str
end

--------------------------------------------------------------
-- 说明：url加密
-- 参数：s_str		待加密串
-- 返回：s			加密串
this.encode_s7Url = function(s_str)
	local s_b64chars, s_s64, s_b64char = s_m_myb64chars, ''
	local n_bytesNum, n_buf
	while #s_str > 0 do
		n_bytesNum = 0
		n_buf = 0
		for byte_cnt = 1, 3 do
			n_buf = (n_buf * 256)
			if #s_str > 0 then
				n_buf = n_buf + string_byte(s_str, 1, 1)
				s_str = string_sub(s_str, 2)
				n_bytesNum = n_bytesNum + 1
			end
		end

		s_b64char = ''
		for i = 1, (n_bytesNum + 1) do
			s_b64char = math_fmod(math.floor(n_buf / 262144), 64) + 1
			s_s64 = s_s64 .. string_sub(s_b64chars, s_b64char, s_b64char)
			n_buf = n_buf * 64
		end

		for i = 1,(3 - n_bytesNum) do
			s_s64 = s_s64 .. '_'
		end
	end
	return s_s64
end

--------------------------------------------------------------
-- 说明：计算md5
-- 参数：n_uin
--		n_time
--		s_s2t
-- 返回：s		md5
this.gen_md5_1 = function(n_uin, n_time, s_s2t, b_isAct)
	if b_isAct then
		return ngx.md5(n_time .. gen_S2Salt(n_uin, s_s2t) .. n_uin)
	else
		return ngx.md5(n_time .. '#miniw_907#' .. n_uin)
	end
end

--------------------------------------------------------------
-- 说明：玩家数据进入队列(三个座位一节车厢)
-- 参数：n_uin
--		s_lang			语言
this.insert_train = function(n_uin, s_lang)
	if t_m_hash[n_uin] then return end
	n_m_str = n_m_count == 0 and tostring(n_uin) or (n_m_str .. '|' .. n_uin)
	t_m_hash[n_uin] = 1
	n_m_count = n_m_count + 1
	if n_m_count == 3 then
		t_m_hash = {}
		n_m_count = 0
		local n_newval = t_coopCache:incr('cursor' .. s_lang, 1, -1)
		t_coopCache:set('train' .. s_lang .. '_' .. n_newval%mis_config.n_coopTrainLen, n_m_str, 0, n_newval)
	end
end

--------------------------------------------------------------
-- 说明：读取玩家数据
-- 参数：n_pos			位置
--		s_lang			语言
-- 返回：t_				玩家数据列表
--		or nil			请求位置超过了火车头
this.read_train = function(n_pos, s_lang)
	local n_base = t_coopCache:get('cursor' .. s_lang)
	if not n_base or n_pos > n_base then
		return nil
	end
	return t_coopCache:get('train' .. s_lang .. '_' .. (n_pos%mis_config.n_coopTrainLen))
end

--------------------------------------------------------------
-- 说明：判断当前服务器是否主节点
-- 返回 bool 			是否主节点
this.is_master_server = function()
	local b_master = false

	if mis_env.n_mis_master and mis_env.n_mis_master == 0 then
		b_master = true
	end

	return b_master
end

--------------------------------------------------------------
-- 说明：36进制递增算法（每次只加1）
-- 字符范围：0-9-A-Z
local incr_int36 = function(s_num)
	local n_val = string_byte(s_num)
	-- 范围检查
	if not ((n_0 <= n_val and n_val <= n_9) or (n_A <= n_val and n_val <= n_Z)) then
		mis_log.error("invalid num: " .. s_num)
		return
	end

	-- 递增
	if n_val == n_9 then
		return 0, "A"
	elseif n_val == n_Z then
		return 1, "0"
	else
		return 0, string_char(n_val + 1)
	end
end

--------------------------------------------------------------
-- 说明：36进制递增算法（每次只加1）
-- 字符范围：0-9-A-Z
this.incr_int36 = function(s_num)
	local n_i
	local t_new_bit = {}
	for n_i = #s_num, 1, -1 do
		-- 字符递增
		local n_carry, s_val = incr_int36(s_num[n_i])
		if n_carry == nil then
			-- 计算出错
			return nil
		elseif n_carry == 1 then
			-- 产生进位，继续递增
			table_insert(t_new_bit, 1, s_val)
			if n_i == 1 then
				-- 最高位进位
				table_insert(t_new_bit, 1, "1")
			end
		else
			-- 没有进位
			break
		end
	end
	-- 递增后的值
	local s_new = string_sub(s_num, 1, n_i - 1) .. table_concat(t_new_bit, "")
	return s_new
end

-- 说明：转为36进制字符
-- 字符范围：0-9-A-Z
local to_uint36 = function(n_num)
	if 0 <= n_num and n_num <= 9 then
		return '' .. n_num
	elseif 10 <= n_num and n_num <= 35 then
		return string_char(n_num + 55)
	else
		mis_log.error("invalid num: " .. n_num)
	end
end

--------------------------------------------------------------
-- 说明：转为36进制数
-- 字符范围：0-9-A-Z
this.to_uint36 = function(n_num, length)
	local n_r
	local t_bit = {}
	while n_num > 35 do
		n_r = n_num % 36
		n_num = math_floor(n_num / 36)
		table_insert(t_bit, 1, to_uint36(n_r))
	end
	table_insert(t_bit, 1, to_uint36(n_num))

	local s_num = table_concat(t_bit, "")
	if s_num and length and #s_num < length then
		local s_fmt = "%0" .. (length - #s_num) .. "d%s"
		s_num = string_format(s_fmt, 0, s_num)
	end
	return s_num
end

--------------------------------------------------------------
-- 说明：生成音乐节密码
this.new_concert_code = function(t_prefix)
	if #t_prefix == 0 then
		return nil, "prefix empty"
	end
	local n_val = t_cache:incr("concert_code", 1, n_INIT_CODE)
	if n_val > n_MAX_CODE then
		-- 重置密码
		local b_ok, s_err = t_cache:add("concert_code_lock", 1, 3)
		if b_ok then
			t_cache:delete("concert_code")
			t_cache:delete("concert_code_lock")
		else
			mis_log.warning("concert code lock fail: " .. tostring(s_err))
			-- 等锁释放
			ngx.sleep(1)
		end
		n_val = t_cache:incr("concert_code", 1, -1)
		if n_val > n_MAX_CODE then
			mis_log.error("concert code overflow 2: " .. n_val)
			return nil, "concert code overflow"
		end
	end
	local s_prefix = t_prefix[math_random(1, #t_prefix)]
	return s_prefix .. mis_env.host_id .. this.to_uint36(n_val, 5)
end

--------------------------------------------------------------
-- 说明：生成音乐节密码
this.new_concert_code_v2 = function(t_prefix)
	if #t_prefix == 0 then
		return nil, "prefix empty"
	end
	local n_val = t_cache:incr("concert_code", 1, n_INIT_CODE)
	if n_val > n_MAX_CODE then
		local n_time = t_cache:get("concert_code_refresh_time")
		if n_time == nil then
			n_time = uu.now()
			t_cache:set("concert_code_refresh_time", n_time)
		end
		if os_date("%Y%m%d", uu.now()) == os_date("%Y%m%d", n_time) then
			mis_log.error("concert code overflow: " .. n_val)
			return
		else
			t_cache:set("concert_code_refresh_time", n_time)
			-- 重置密码
			local b_ok, s_err = t_cache:add("concert_code_lock", 1, 3)
			if b_ok then
				t_cache:delete("concert_code")
				t_cache:delete("concert_code_lock")
			else
				mis_log.error("concert code lock fail: " .. tostring(s_err))
				return
			end
			n_val = t_cache:incr("concert_code", 1, -1)
			if n_val > n_MAX_CODE then
				mis_log.error("concert code overflow 2: " .. n_val)
				return
			end
		end
	end
	local s_prefix = t_prefix[math_random(1, #t_prefix)]
	return s_prefix .. mis_env.host_id .. this.to_uint36(n_val, 5)
end

--------------------------------------------------------------
-- 说明：计算音乐节密码哈希值
this.concert_code_hash = function(s_code)
	return string_byte(string_sub(s_code, #s_code, #s_code))
end

--------------------------------------------------------------
-- 说明：生成迎财神助力密码
this.new_wealth_god_code = function()
	
	local n_val = t_cache:incr("wealth_god_code", 1, n_INIT_CODE)
	if n_val > n_MAX_CODE then
		-- 重置密码
		local b_ok, s_err = t_cache:add("wealth_god_code_lock", 1, 3)
		if b_ok then
			t_cache:delete("wealth_god_code")
			t_cache:delete("wealth_god_code_lock")
		else
			mis_log.warning("wealth_god_code lock fail: " .. tostring(s_err))
			-- 等锁释放
			ngx.sleep(1)
		end
		n_val = t_cache:incr("wealth_god_code", 1, -1)
		if n_val > n_MAX_CODE then
			mis_log.error("wealth_god_code overflow 2: " .. n_val)
			return nil, "wealth_god_code overflow"
		end
	end
	local s_prefix = '迷你财神'
	local flag_hash = ngx.ctx.t_params.uin % #mis_config.t_ssdbDc.t_select
	return s_prefix .. flag_hash .. this.to_uint36(n_val, 5)
end

--------------------------------------------------------------
-- 说明：计算迎财神密码哈希值
this.wealth_god_code_hash = function(s_code)
	return string_byte(string_sub(s_code, #s_code, #s_code))
end

--------------------------------------------------------------
-- 说明：生成分享密码
this.new_share_code = function(s_type, t_prefix, n_init, n_max, f_format)
	if #t_prefix == 0 then
		return nil, "prefix empty"
	end
	local s_codeKey = s_type .. "_share_code"
	local n_val = t_cache:incr(s_codeKey, 1, n_init or n_INIT_CODE)
	if n_val > (n_max or n_MAX_CODE) then
		-- 重置密码
		local b_ok, s_err = t_cache:add(s_codeKey .. "_lock", 1, 3)
		if b_ok then
			t_cache:delete(s_codeKey)
			t_cache:delete(s_codeKey .. "_lock")
		else
			mis_log.warning(s_codeKey .. " lock fail: " .. tostring(s_err))
			-- 等锁释放
			ngx.sleep(1)
		end
		n_val = t_cache:incr(s_codeKey, 1, -1)
		if n_val > (n_max or n_MAX_CODE) then
			mis_log.error(s_codeKey .. " overflow: " .. n_val)
			return nil, "share code overflow"
		end
	end
	local s_prefix = t_prefix[math_random(1, #t_prefix)]
	if type(f_format) == "function" then
		return s_prefix .. mis_env.host_id .. f_format(n_val)
	else
		return s_prefix .. mis_env.host_id .. this.to_uint36(n_val, 5)
	end
end

--------------------------------------------------------------
-- 说明：计算分享密码哈希值
this.share_code_hash = function(s_code)
	return string_byte(string_sub(s_code, #s_code, #s_code))
end

--------------------------------------------------------------
-- 说明：分段随机（每个分段随机选择一个）
-- 参数：t				样本
--		n_part			分段数
-- 返回：t_ret			数据列表
this.segment_random = function(t_sample, n_part)
	local t_ret = {}
	if #t_sample >= n_part then
		local n_uint = math_floor(#t_sample / n_part)
		local n_start, n_stop
		for i = 1, n_part do
			n_start = 1 + n_uint * (i - 1)
			if i == n_part then
				n_stop = #t_sample
			else
				n_stop = math_min(n_start + n_uint - 1, #t_sample)
			end
			table_insert(t_ret, t_sample[math_random(n_start, n_stop)])
		end
	else
		for i = 1, #t_sample do
			table_insert(t_ret, t_sample[i])
		end
	end
	return t_ret
end

--------------------------------------------------------------
-- 说明：分段随机（先分段，再每个分段随机选择一个）
-- 参数：n_start		起始数值
--		n_stop			结束数值
--		n_part			分段数
--		n_select		选中分段
--		n_select_rand	选中分段中再随机选择N个
-- 返回：t_ret			数据列表
this.segment_range_random = function(n_start, n_stop, n_part)
	local t_ret = {}
	local n_length = n_stop - n_start + 1
	if n_length >= n_part then
		local n_uint = math_floor(n_length / n_part)
		local n_a, n_b
		for i = 1, n_part do
			n_a = n_start + n_uint * (i - 1)
			if i == n_part then
				n_b = n_stop
			else
				n_b = math_min(n_a + n_uint - 1, n_stop)
			end
			table_insert(t_ret, math_random(n_a, n_b))
		end
	else
		for i = n_start, n_stop do
			table_insert(t_ret, i)
		end
	end
	return t_ret
end

--------------------------------------------------------------
-- 说明：分段随机（先分段，再从其中一段随机选择N个）
-- 参数：t				样本
--		n_part			分段数
--		n_select		选中分段
--		n_select_rand	选中分段中再随机选择N个
-- 返回：t_ret			数据列表
this.segment_select_random = function(t_sample, n_part, n_select, n_select_rand)
	local t_select
	if #t_sample >= n_part then
		n_select = (n_select - 1) % n_part
		local n_uint = math_floor(#t_sample / n_part)
		local n_start = 1 + n_uint * n_select
		local n_stop
		if n_select == (n_part - 1) then
			n_stop = #t_sample
		else
			n_stop = math_min(n_start + n_uint - 1, #t_sample)
		end
		t_select = this.segment_range_random(n_start, n_stop, n_select_rand)
	else
		t_select = this.segment_range_random(1, #t_sample, n_select_rand)
	end
	
	local t_ret = {}
	for i = 1, #t_select do
		table_insert(t_ret, t_sample[t_select[i]])
	end
	return t_ret
end


--=====================================================================================
--在做第二期三丽鸥任务活动时，觉得还是放到一个公共文件里比较好，后续都用mis_tools里的代码
--这里由于一期线上代码还会用到所以保留，
--date转ts
this.data2ts = function (date)
	if not date then 
		return uu.now()
	end

	local n1,n2,n3 = string.match(date, "(%d+)-(%d+)-(%d+)")
	local h, m, s = string.match(date, "(%d+):(%d+):(%d+)")
	return os.time({year=n1, month=n2,day=n3, hour=h, min=m, sec=s})
end

--检查总控
this.check_control = function (t_conf)
	if t_conf.ctrl_cfg and t_conf.ctrl_cfg.control~=nil and (not t_conf.ctrl_cfg.control) then 
		return false
	end

	return true
end

--检查时间控制
this.check_time = function (t_conf)
	local start_ts
	local end_ts

	if t_conf.ctrl_cfg and t_conf.ctrl_cfg.start_time then 
		start_ts = this.data2ts(t_conf.ctrl_cfg.start_time)
	end

	if t_conf.ctrl_cfg and t_conf.ctrl_cfg.end_time then 
		end_ts = this.data2ts(t_conf.ctrl_cfg.end_time)
	end

	local now_time = uu.now()
	if start_ts then 
		if start_ts > now_time then 
			return false
		end
	end

	if end_ts then 
		if end_ts < now_time then 
			return false
		end
	end
	
	return true
end

--检查版本
this.check_version = function (t_conf, ver)
	local ver_min
	local ver_max

	if t_conf.ctrl_cfg and t_conf.ctrl_cfg.version_min then 
		ver_min = uu.ver2i(t_conf.ctrl_cfg.version_min)

		--log.debug("t_conf.ctrl_cfg.version_min="..(t_conf.ctrl_cfg.version_min))
		--log.debug("ver_min="..ver_min)
	end

	if t_conf.ctrl_cfg and t_conf.ctrl_cfg.version_max then 
		ver_max = uu.ver2i(t_conf.ctrl_cfg.version_max)

		--log.debug("t_conf.ctrl_cfg.version_max="..(t_conf.ctrl_cfg.version_min))
		--log.debug("ver_max="..ver_max)
	end
	local n_ver = uu.ver2i(ver)

	--log.debug("ver="..ver)
	--log.debug("n_ver="..n_ver)

	--
	if ver_min then 
		if n_ver < ver_min then 
			return false
		end
	end

	if ver_max then 
		if n_ver > ver_max then 
			return false
		end
	end

	return true
end

--检查渠道限制
this.check_apiid = function (t_conf, check_apiid)
	--log.debug("t_conf.ctrl_cfg.apiids_no="..(t_conf.ctrl_cfg.apiids_no or "nil"))
	--log.debug("t_conf.ctrl_cfg.ap2222="..type(t_conf.ctrl_cfg.apiids_no))
	--log.debug("check_apiid="..(check_apiid or "nil"))
	--若有不允许的渠道
	if t_conf.ctrl_cfg and t_conf.ctrl_cfg.apiids_no then 
		for n in string.gmatch(t_conf.ctrl_cfg.apiids_no, "([^%,]+)") do
			--log.debug("apiids_no="..(tonumber(n) or "nil"))
    		if tonumber(check_apiid)==tonumber(n) then 
				return false
			end
		end
	end

	--若有渠道控制
	if t_conf.ctrl_cfg and t_conf.ctrl_cfg.apiids then 
		local find = false
		for n in string.gmatch(t_conf.ctrl_cfg.apiids, "([^%,]+)") do
			--log.debug("apiids="..(tonumber(n) or "nil"))
    		if tonumber(check_apiid)==tonumber(n) then 
				find = true
			end
		end
		if find then 
			return true
		else
			return false
		end
	end

	return true
end

--对总控，渠道，版本，时间都做校验
this.check_limit = function (t_conf, check_apiid, ver)
	if not t_conf then 
		return false, "check t_conf error"
	end

	if this.check_control(t_conf)==false then 
		return false, "check_control false"
	end

	if this.check_apiid(t_conf, check_apiid)==false then 
		return false, "check_apiid false"
	end

	if this.check_version(t_conf, ver)==false then 
		return false, "check_version false"
	end

	if this.check_time(t_conf)==false then 
		return false, "check_time false"
	end

	return true
end

this.get_day_zero = function (ts)
	ts = ts or uu.now()
	--取年月日， 时分秒给0，即得当天0点时间戳
	local ts_table = os.date("*t", ts)
	local zero_ts = os.time({year=ts_table.year, month=ts_table.month, day=ts_table.day,
		hour=0, min=0, sec=0})

	return zero_ts
end

this.check_day_delta = function(day1, other_day)
	local day1_zero = this.get_day_zero(day1)
	
	local delta = math.abs(other_day - day1_zero)
	local delta_f = tonumber(string.format("%.2f", delta/86400))

	return delta/86400
end

--检测任务复位
this.check_task_reset = function (task)
	if not task.finish_ts then 
		return true
	end
	
	local delta = this.check_day_delta(task.finish_ts, uu.now())
	if delta >= tonumber(task.daily) then 
		return true
	end

	return false
end
--=====================================================================================

return this